package org.example.editor;

import org.example.command.Command;
import org.example.log.SessionLog;
import org.example.strategy.*;

import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class Editor {
    private EditorContext editorContext;
    private SessionLog sessionLog;
    private ArrayList<String> loadedFileName;
    private ArrayList<String> loadedFilePath;
    private ArrayList<Long> fileOpenedTime;
    private Map<String, LinkedList<String>> fileNameAndContent;
    private String currentFilePath = null;
    private String currentFileName = null;
    private ArrayList<Boolean> isLoadedFileChanged;

    private String lastCommand1 = "";
    private String lastCommand2 = "";
    private Boolean lastState1;
    private Boolean lastState2;
    private LinkedList<String> historyContents1;
    private LinkedList<String> historyContents2;
    private Integer lastChangedFileIndex;

    public Editor() {
        this.editorContext = new EditorContext();
        this.fileNameAndContent = new HashMap<>();
        this.loadedFileName = new ArrayList<>();
        this.loadedFilePath = new ArrayList<>();
        this.fileOpenedTime = new ArrayList<>();
        this.isLoadedFileChanged = new ArrayList<>();
        this.sessionLog = new SessionLog();

        this.historyContents1 = new LinkedList<>();
        this.historyContents2 = new LinkedList<>();
    }
    public String getCurrentFilePath(){
        return currentFilePath;
    }
    public Integer getCurrentFileNumber(){
        return loadedFileName.indexOf(currentFileName);
    }
    public Integer getTotalFileNumber(){
        return this.loadedFileName.size();
    }
    public void loadFile(String filePath){
        if (!isValidFilePath(filePath)){
            System.out.println("Invalid file path:" + filePath);
            return;
        }
        if (currentFilePath != null && currentFilePath.equals(filePath)){
            System.out.println("File is already open:" + filePath);
            return;
        }
        LoadFileStrategy loadFileStrategy = new LoadFileStrategy(filePath);
        editorContext.setEditorStrategy(loadFileStrategy);
        editorContext.performEditorAction();
        this.currentFilePath = filePath;
        this.currentFileName = loadFileStrategy.getFileName();

        this.fileNameAndContent.put(loadFileStrategy.getFileName(), loadFileStrategy.getFileContent());
        this.loadedFileName.add(this.currentFileName);
        this.loadedFilePath.add(this.currentFilePath);
        this.fileOpenedTime.add(System.currentTimeMillis());
        this.isLoadedFileChanged.add(false);
    }
    public SessionLog getSessionLog(){
        return sessionLog;
    }
    public void saveFile(Integer fileNumber){
        if (!isLoadedFileChanged.get(fileNumber - 1))return;
        String fileName = this.loadedFileName.get(fileNumber - 1);
        Integer index = getCurrentFileNumber();
        LinkedList<String> contents = this.fileNameAndContent.get(fileName);
        editorContext.setEditorStrategy(new SaveFileStrategy(loadedFilePath.get(fileNumber - 1),contents));
        editorContext.performEditorAction();
        String savedName = fileName.replace("*","");
        loadedFileName.set(fileNumber - 1,savedName);
        this.fileNameAndContent.remove(fileName);
        this.fileNameAndContent.put(savedName,contents);
        this.isLoadedFileChanged.set(fileNumber - 1,false);
        if (index == fileNumber - 1)currentFileName = savedName;
    }
    public void closeFile(Integer fileNumber, Scanner scanner){
        if (fileNumber > loadedFileName.size()){
            System.out.println("Please enter the correct number!");
            return;
        }
        if (isLoadedFileChanged.get(fileNumber - 1)){
            System.out.println("Save changed file before closing?(y/n)");
            String choice = scanner.nextLine();
            if (choice.equalsIgnoreCase("y"))saveFile(fileNumber);
        }
        Integer index = getCurrentFileNumber();
        CloseFileStrategy closeFileStrategy = new CloseFileStrategy(fileNumber,loadedFileName,loadedFilePath,isLoadedFileChanged,fileNameAndContent);
        sessionLog.timesAdd(loadedFilePath.get(fileNumber - 1),fileOpenedTime.get(fileNumber - 1));

        editorContext.setEditorStrategy(closeFileStrategy);
        editorContext.performEditorAction();

        this.isLoadedFileChanged = closeFileStrategy.getIsLoadedFileChanged();
        this.loadedFileName = closeFileStrategy.getLoadedFileName();
        this.loadedFilePath = closeFileStrategy.getLoadedFilePath();
        this.fileNameAndContent = closeFileStrategy.getNameAndContent();
        this.fileOpenedTime.remove(fileNumber - 1);

        if ((fileNumber - 1) == index){
            if (index == 0 && loadedFileName.size() != 0){
                currentFileName = loadedFileName.get(index);
                currentFilePath = loadedFilePath.get(index);
            } else if (loadedFileName.size() == 0) {
                currentFilePath = "";
                currentFileName = "";
            }else {
                currentFileName = loadedFileName.get(index - 1);
                currentFilePath = loadedFilePath.get(index - 1);
            }
        }
    }
    public void wsFile(){
        editorContext.setEditorStrategy(new WSStrategy(this.loadedFileName,this.currentFileName));
        editorContext.performEditorAction();
    }
    public void switchFile(Integer fileNumber){
        if (fileNumber > loadedFileName.size()){
            System.out.println("Please enter the correct number!");
            return;
        }
        SwitchStrategy switchStrategy = new SwitchStrategy(fileNumber,this.loadedFileName,this.loadedFilePath);
        editorContext.setEditorStrategy(switchStrategy);
        editorContext.performEditorAction();
        this.currentFileName = switchStrategy.getFileName();
        this.currentFilePath = switchStrategy.getFilePath();
    }
    public void insert(Integer lineNumber,String content){
        InsertStrategy insertStrategy = new InsertStrategy(lineNumber,content,this.fileNameAndContent.get(currentFileName));
        editorContext.setEditorStrategy(insertStrategy);
        editorContext.performEditorAction();
        Integer index = getCurrentFileNumber();
        this.isLoadedFileChanged.set(index,true);
        if (!currentFileName.endsWith("*")){
            this.fileNameAndContent.remove(currentFileName);
            currentFileName = currentFileName + "*";
        }
        this.loadedFileName.set(index,currentFileName);
        this.fileNameAndContent.put(currentFileName,insertStrategy.getFileContents());
    }
    public void appendHead(String content){
        insert(1,content);
    }
    public void appendTail(String content){
        insert(-1,content);
    }
    public void delete(String content){
        DeleteStrategy deleteStrategy = new DeleteStrategy(content,this.fileNameAndContent.get(currentFileName));
        editorContext.setEditorStrategy(deleteStrategy);
        editorContext.performEditorAction();
        Boolean isFileChanged = deleteStrategy.getIsFileChanged();
        if (isFileChanged){
            this.isLoadedFileChanged.set(getCurrentFileNumber(), true);
            if (!currentFileName.endsWith("*")){
                this.fileNameAndContent.remove(currentFileName);
                this.loadedFileName.set(getCurrentFileNumber(),currentFileName + "*");
                currentFileName = currentFileName + "*";
            }
            this.fileNameAndContent.put(currentFileName,deleteStrategy.getFileContents());

        }
    }
    public void list(){
        editorContext.setEditorStrategy(new ListStrategy(this.fileNameAndContent.get(currentFileName)));
        editorContext.performEditorAction();
    }
    public void listTree(String title){
        Integer index = this.fileNameAndContent.get(currentFileName).indexOf(title);
        editorContext.setEditorStrategy(new ListTreeStrategy(this.fileNameAndContent.get(currentFileName),index));
        editorContext.performEditorAction();
    }
    // 应该也不能undo undo吧？
    public void undo(){
        boolean can = lastCommand1.equals("undo") || lastCommand1.equals("")
                || lastCommand1.equals("load") || lastCommand1.equals("save")
                || lastCommand1.equals("close") || lastCommand1.equals("switch");
        if (can){
            System.out.println("No command can undo!");
            return;
        }
        Boolean currentState = isLoadedFileChanged.get(lastChangedFileIndex);
        if (currentState != lastState1){
            String name = currentFileName.replace("*","");
            isLoadedFileChanged.set(lastChangedFileIndex, lastState1);
            loadedFileName.set(lastChangedFileIndex,name);
            fileNameAndContent.remove(currentFileName);
            currentFileName = name;
        }
        fileNameAndContent.put(currentFileName,historyContents1);
    }
    public void redo(){
        if (!lastCommand1.equals("undo")){
            System.out.println("Can't redo!");
            return;
        }
        Boolean currentState = isLoadedFileChanged.get(lastChangedFileIndex);
        if (currentState != lastState1){
            String name = currentFileName + "*";
            isLoadedFileChanged.set(lastChangedFileIndex, lastState1);
            loadedFileName.set(lastChangedFileIndex,name);
            fileNameAndContent.remove(currentFileName);
            currentFileName = name;
        }
        fileNameAndContent.put(currentFileName,historyContents1);
    }
    public void history(long number){
        editorContext.setEditorStrategy(new HistoryStrategy(number));
        editorContext.performEditorAction();
    }
    public void stats(String choice){
        if (choice.equals("current"))sessionLog.showCurrent(loadedFilePath,fileOpenedTime);
        else sessionLog.showAll();
    }
    public void executeCommand(Command command){
        switch (command.getClass().getName()){
            case "org.example.command.AppendHeadCommand":{
                storeLastCommand("append-head");
                break;
            }
            case "org.example.command.AppendTailCommand":{
                storeLastCommand("append-tail");
                break;
            }
            case "org.example.command.DeleteCommand":{
                storeLastCommand("delete");
                break;
            }
            case "org.example.command.InsertCommand":{
                storeLastCommand("insert");
                break;
            }
            case "org.example.command.UndoCommand":{
                storeLastCommand("undo");
                break;
            }
            case "org.example.command.RedoCommand":{
                storeLastCommand("redo");
                break;
            }

            case "org.example.command.LoadFileCommand":{
                this.lastCommand1 = lastCommand2;
                this.lastCommand2 = "load";
                break;
            }
            case "org.example.command.SaveFileCommand":{
                this.lastCommand1 = lastCommand2;
                this.lastCommand2 = "save";
                break;
            }
            case "org.example.command.SwitchCommand":{
                this.lastCommand1 = lastCommand2;
                this.lastCommand2 = "switch";
                break;
            }
            case "org.example.command.CloseFileCommand":{
                this.lastCommand1 = lastCommand2;
                this.lastCommand2 = "close";
                break;
            }

            case "org.example.command.StatsCommand":
            case "org.example.command.HistoryCommand":
//            case "org.example.command.DirTreeCommand":
            case "org.example.command.ListCommand":
            case "org.example.command.ListTreeCommand":
            case "org.example.command.WSCommand":break;
        }
        command.excute();
    }
    private void storeLastCommand(String lastCommand){
        this.lastCommand1 = this.lastCommand2;
        this.lastCommand2 = lastCommand;

        // 这里需要额外注意说明 Java中的列表的互相赋值不是直接创建新的列表对象，而是指向同一个列表对象 修改会导致共同改变
        this.historyContents1 = new LinkedList<>(historyContents2);
        this.historyContents2 = new LinkedList<>(this.fileNameAndContent.get(currentFileName));

        this.lastChangedFileIndex = getCurrentFileNumber();

        this.lastState1 = lastState2;
        this.lastState2 = this.isLoadedFileChanged.get(lastChangedFileIndex);
    }
    public boolean isValidFilePath(String filePath){
        try {
            // 使用Java的Path类进行路径解析，如果解析失败则抛出异常
            Path path = Paths.get(filePath);
            String fileName = path.getFileName().toString();
            // 检查文件名长度
            if (fileName.length() > 255) {
                System.out.println("File name is too long.");
                return false;
            }

            // 检查是否包含非法字符
            if (!isPathValidChars(path.toString())) {
                System.out.println("File path contains invalid characters.");
                return false;
            }
            // 其他检查可以根据需要添加
            int lastDotIndex = fileName.lastIndexOf(".");
            if (lastDotIndex != -1 && lastDotIndex < fileName.length() - 1){
                String fileExtension = fileName.substring(lastDotIndex+1);
                if (fileExtension == null || !fileExtension.equalsIgnoreCase("md")){
                    return false;
                }
            }
            return true;
        } catch (InvalidPathException e) {
            System.out.println("Invalid file path: " + e.getMessage());
            return false;
        }
    }
    private boolean isPathValidChars(String path) {
        String invalidChars = "<>:\"/\\|?*";

        for (char c : invalidChars.toCharArray()) {
            if (path.indexOf(c) != -1) {
                return false;
            }
        }
        return true;
    }
}
